﻿#include "modbustcp.h"

ModbusTCP::ModbusTCP(networkParameters netPara)
    : NetworkCommunication(CommunicationProtocol::ModBus, netPara)
{
    //当有可读数据时触发的槽函数
    connect(tcpSocket,&QTcpSocket::readyRead,[=](){
        //将可读的数据存入数组
        bool res = MessageReceive(ReceiveMessage);
        if(res)
        {
            if(TCPAnalysisMessage(ReceiveMessage, RequestMessage))
            {
                RequestMessage.clear();
            }
        }

    });
}

//TCP主站发送请求报文函数
//1
bool ModbusTCP::TCPRequestMessageSend(const ModbusTcpBasicInformation &MTBasicInfo)
{
    //获取参数，存入成员变量
    basicInfo = MTBasicInfo;
    bool res = TCPRequestMessageSend(basicInfo, writeMessage);

    return  res;
}
//2
bool ModbusTCP::TCPRequestMessageSend(const ModbusTcpBasicInformation &MTBasicInfo, const QByteArray &byteArr)
{
    writeMessage = NULL;
    //获取参数，存入成员变量
    basicInfo = MTBasicInfo;
    writeMessage = byteArr;

    //初始化判断结果变量
    bool res = false;
    //根据报文基础信息结构体进行后续操作
    //先判断写入报文状态
    if(writeMessage.isNull())
    {
        switch(basicInfo.funcCode)
        {
        case 1:
        {
            RequestMessage0x010x03Build(RequestMessage);
            break;
        }
        case 3:
        {
            RequestMessage0x010x03Build(RequestMessage);
            break;
        }
        default:
            return res = false;
        }
    }
    else
    {
        switch(basicInfo.funcCode)
        {
        case 15:
        {
            RequestMessage0x0F0x10Build(RequestMessage);
            break;
        }
        case 16:
        {
            RequestMessage0x0F0x10Build(RequestMessage);
            break;
        }
        default:
            return res = false;
        }
    }

    res = MessageSend(RequestMessage);

    return res;
}

//读输出线圈0x01和读输出寄存器0x03请求报文构建函数
void ModbusTCP::RequestMessage0x010x03Build(QByteArray &requestMess)
{
    //事务元标识符赋值
    basicInfo.transactionIdenti = transactionIdentifier++;
    //设置请求报文数组大小为12，即该报文长度为12字节
    requestMess.resize(REQUEST_MESSAGE_LENGTH_0X01_0X03);

    /*********************************将数据放入报文数组*********************************/
    //事务元标识符：2字节
    requestMess[0] = basicInfo.transactionIdenti >> 8;
    requestMess[1] = basicInfo.transactionIdenti & 0xff;
    //协议标识符：2字节
    requestMess[2] = basicInfo.protocolIdenti >> 8;
    requestMess[3] = basicInfo.protocolIdenti & 0x0ff;
    //长度标识符：2字节
    requestMess[4] = basicInfo.length >> 8;
    requestMess[5] = basicInfo.length & 0x0ff;
    //单元标识符：1字节
    requestMess[6] = basicInfo.unitIdenti;
    //功能码：1字节
    requestMess[7] = basicInfo.funcCode;
    //起始地址：2字节
    requestMess[8] = basicInfo.beginAddr >> 8;
    requestMess[9] = basicInfo.beginAddr & 0x0ff;
    //数量：2字节
    requestMess[10] = basicInfo.num >> 8;
    requestMess[11] = basicInfo.num & 0x0ff;
}

//写多个输出线圈0x0F和写多个输出寄存器0x10请求报构建函数
void ModbusTCP::RequestMessage0x0F0x10Build(QByteArray &requestMess)
{
    /*****************************************获取字节数*************************************/
    quint8 numOfByte = writeMessage.size();
    /*****************************************获取长度项*************************************/
    quint16 length = basicInfo.length + numOfByte + 1;

    //事务元标识符赋值
    basicInfo.transactionIdenti = transactionIdentifier++;
    //设置请求报文数组大小
    requestMess.resize(REQUEST_MESSAGE_LENGTH_0X01_0X03 + 1 + numOfByte);
    /********************************MBAP报文头部分********************************/
    //事务元标识符：2字节
    requestMess[0] = basicInfo.transactionIdenti >> 8;
    requestMess[1] = basicInfo.transactionIdenti & 0xff;
    //协议标识符：2字节
    requestMess[2] = basicInfo.protocolIdenti >> 8;
    requestMess[3] = basicInfo.protocolIdenti & 0x0ff;
    //长度标识符：2字节
    requestMess[4] = length >> 8;
    requestMess[5] = length & 0x0ff;
    //单元标识符：1字节
    requestMess[6] = basicInfo.unitIdenti;
    //功能码：1字节
    requestMess[7] = basicInfo.funcCode;
    //起始地址：2字节
    requestMess[8] = basicInfo.beginAddr >> 8;
    requestMess[9] = basicInfo.beginAddr & 0x0ff;
    //数量：2字节
    requestMess[10] = basicInfo.num >> 8;
    requestMess[11] = basicInfo.num & 0x0ff;
    //字节数：1字节
    requestMess[12] = (quint8)numOfByte;
    //字节内容
    for(int i = 0; i < numOfByte; i++)
    {
        requestMess[13 + i] = writeMessage[i];
    }
}

//解析响应报文
bool ModbusTCP::TCPAnalysisMessage(const QByteArray &receiveMessageArr, const QByteArray &requestMessageArr)
{
    //判断当前主站状态，若是等待消息状态则显示消息，否则提示不是需要的消息
    if(!networkSendIdentifier)
    {
        return false;
    }

    /***************************************解析报文*************************************/
    //报文合法性判断
    bool analysisResult = MessageLegalJudge(receiveMessageArr, requestMessageArr);
    if(!analysisResult)
    {
        return false;
    }

    //处理匹配的正常响应码
    switch(receiveMessageArr.at(7))
    {
    case 1:
    {
        //0X01功能码报文处理函数
        analysisResult = TCP0X01FuncCodeProcess(receiveMessageArr, requestMessageArr, readMessage);
        break;
    }

    case 3:
    {
        //0X03功能码报文处理函数
        analysisResult = TCP0X03FuncCodeProcess(receiveMessageArr, requestMessageArr, readMessage);
        break;
    }

    case 15:
    {
        //0X0f功能码报文处理函数
        analysisResult = TCP0X0fFuncCodeProcess(receiveMessageArr, requestMessageArr);
        break;
    }

    case 16:
    {
        //0X10功能码报文处理函数
        analysisResult = TCP0X10FuncCodeProcess(receiveMessageArr, requestMessageArr);
        break;
    }
    }

    return analysisResult;
}

//报文合法性判断函数
bool ModbusTCP::MessageLegalJudge(const QByteArray &MessageArr, const QByteArray &requestMessageArr)
{
    //1. 判断接收到的报文长度是否合法，合法最小长度为写入请求报文的异常响应报文，为9字节
    if(MessageArr.size() < 9)
    {
        return false;
    }

    //2. 判断接收到的报文与请求报文的事务元标识符是否一致
    if((MessageArr.at(0) != requestMessageArr.at(0)) || (MessageArr.at(1) != requestMessageArr.at(1)))
    {
        return false;
    }

    //3. 判断接收到的报文的协议标识是否是Modbus协议
    if((MessageArr.at(2) != 0) || (MessageArr.at(3) != 0))
    {
        return false;
    }

    //4. 判断接收到的报文的长度的数据与其后的字节长度是否匹配
    if((MessageArr.size() - 6) != BondTwoUint8ToUint16(MessageArr.at(4),MessageArr.at(5)))
    {
        return false;
    }

    //5. 判断接收到的报文的单元标识符是否与请求报文一致
    if(MessageArr.at(6) != requestMessageArr.at(6))
    {
        return false;
    }

    bool res = false;
    //6. 判断接收到的报文的功能码是否合法
    res = FuncCodeLegalJudge(MessageArr);
    if(res == false)
    {
        return false;
    }

    //7. 处理匹配的异常响应码信息
    QString exceptionPrompt = TCPExceptionCodeProcess(MessageArr);
    if(exceptionPrompt != "")
    {
        return false;
    }

    //8. 判断请求与响应的功能码是否一致
    if(MessageArr.at(7) != requestMessageArr.at(7))
    {
        return false;
    }

    return true;
}

//0X01功能码响应报文处理函数
bool ModbusTCP::TCP0X01FuncCodeProcess(const QByteArray &MessageArr, const QByteArray &requestMessageArr, QString &readData)
{
    readData = "";
    //处理匹配的正常响应码
    quint8 numOfByte;
    quint16 length;

    //判断响应报文的长度是否符合最低要求
    if(MessageArr.size() < 10)
    {
        return false;
    }

    //先判断响应报文的字节数是否和请求报文需要的对应
    //求出请求报文中想读的线圈数量number
    quint16 number = BondTwoUint8ToUint16((quint8)requestMessageArr.at(10),(quint8)requestMessageArr.at(11));

    //判断响应报文的字节数是否和满足number需要的字节数匹配
    //求出字节数
    numOfByte = (number + 7) / 8;

    //如果响应报文的字节数和满足number需要的字节数不匹配，则显示错误信息
    if(numOfByte != (quint8)MessageArr.at(8))
    {
        return false;
    }

    //判断自身长度字段是否异常
    length =(quint16)BondTwoUint8ToUint16(MessageArr.at(4),MessageArr.at(5));
    //如果不匹配，则输出错误信息
    if(length != MessageArr.size() - 6)
    {
        return false;
    }

    //判断自身字节数字段是否异常
    if((quint8)MessageArr.at(8) != (MessageArr.size() - 9))
    {
        return false;
    }

    //取出所读的多个线圈，数据从第九位开始
    for(int i = 0; i < (quint8)MessageArr.at(8); i++)
    {
        //先转化为2进制字符串
        QString str = QString::number((quint8)MessageArr.at(9 + i),2);
        //再转化为2进制整形，由二进制整形转化为8位2进制字符串前面自动补0，从而保证8位
        str = QString("%1").arg((quint8)str.toInt(NULL,2),8,2,QChar('0'));
        //8bit字节倒转
        ByteReverse(str);
        //添加到数据中
        readData += str;
    }
    //去除填充的0位，读出请求报文请求的线圈数
    readData = readData.left(number);

    return true;
}

//0X03功能码响应报文处理函数
bool ModbusTCP::TCP0X03FuncCodeProcess(const QByteArray &MessageArr, const QByteArray &requestMessageArr, QString &readData)
{
    readData = "";
    //处理匹配的正常响应码
    quint16 length;

    //判断响应报文的长度是否符合最低要求
    if(MessageArr.size() < 10)
    {
        return false;
    }

    //先判断响应报文的字节数是否和请求报文需要的对应
    //求出请求报文中想读的寄存器数量number
    quint16 number = BondTwoUint8ToUint16((quint8)requestMessageArr.at(10),(quint8)requestMessageArr.at(11));

    //如果响应报文的字节数和满足number需要的字节数不匹配，则显示错误信息
    if(2*number != (quint8)MessageArr.at(8))
    {
        return false;
    }

    //判断自身长度字段是否异常
    length =(quint16)BondTwoUint8ToUint16(MessageArr.at(4),MessageArr.at(5));
    //如果不匹配，则输出错误信息
    if(length != MessageArr.size() - 6)
    {
        return false;
    }

    //判断自身字节数字段是否异常
    if((quint8)MessageArr.at(8) != (MessageArr.size() - 9))
    {
        return false;
    }

    //取出所读的多个寄存器，并显示，数据从第9个字节开始
    for(int i = 0; i < (quint8)MessageArr.at(8); i += 2)
    {
        readData += QString::number(BondTwoUint8ToUint16((quint8)MessageArr.at(9 + i),(quint8)MessageArr.at(10 + i)));
        readData += " ";
    }

    return true;
}

//0X0f功能码报文处理函数
bool ModbusTCP::TCP0X0fFuncCodeProcess(const QByteArray &MessageArr, const QByteArray &requestMessageArr)
{
    //处理匹配的正常响应码
    quint16 messageArrayBeginAddress;
    quint16 requestMessageBegainAddress;
    quint16 length;
    quint16 messageArrayByteNum;
    quint16 requestMessageByteNum;

    //判断响应报文的长度是否符合最低要求，写入请求报文的响应报文为固定长度12
    if(MessageArr.size() != 12)
    {
        return false;
    }

    //判断自身长度字段是否异常
    length =(quint16)BondTwoUint8ToUint16(MessageArr.at(4),MessageArr.at(5));
    //如果不匹配，则输出错误信息
    if(length != 6)
    {
        return false;
    }

    //判断收到的报文的起始地址字段是否与请求报文匹配
    messageArrayBeginAddress = (quint16)BondTwoUint8ToUint16(MessageArr.at(8),MessageArr.at(9));
    requestMessageBegainAddress = (quint16)BondTwoUint8ToUint16(requestMessageArr.at(8),requestMessageArr.at(9));
    if(messageArrayBeginAddress != requestMessageBegainAddress)
    {
        return false;
    }

    //判断收到的报文的数量字段是否与请求报文匹配
    messageArrayByteNum = (quint16)BondTwoUint8ToUint16(MessageArr.at(10),MessageArr.at(11));
    requestMessageByteNum = (quint16)BondTwoUint8ToUint16(requestMessageArr.at(10),requestMessageArr.at(11));
    if(messageArrayByteNum != requestMessageByteNum)
    {
        return false;
    }

    return true;
}

//10. 0X10功能码报文处理函数
bool ModbusTCP::TCP0X10FuncCodeProcess(const QByteArray &MessageArr, const QByteArray &requestMessageArr)
{
    //处理匹配的正常响应码
    quint16 messageArrayBeginAddress;
    quint16 requestMessageBegainAddress;
    quint16 length;
    quint16 messageArrayByteNum;
    quint16 requestMessageByteNum;

    //判断响应报文的长度是否符合最低要求，写入请求报文的响应报文为固定长度12
    if(MessageArr.size() != 12)
    {
        return false;
    }

    //判断自身长度字段是否异常
    length =(quint16)BondTwoUint8ToUint16(MessageArr.at(4),MessageArr.at(5));
    //如果不匹配，则输出错误信息
    if(length != 6)
    {
        return false;
    }

    //判断收到的报文的起始地址字段是否与请求报文匹配
    messageArrayBeginAddress = (quint16)BondTwoUint8ToUint16(MessageArr.at(8),MessageArr.at(9));
    requestMessageBegainAddress = (quint16)BondTwoUint8ToUint16(requestMessageArr.at(8),requestMessageArr.at(9));
    if(messageArrayBeginAddress != requestMessageBegainAddress)
    {
        return false;
    }

    //判断收到的报文的数量字段是否与请求报文匹配
    messageArrayByteNum = (quint16)BondTwoUint8ToUint16(MessageArr.at(10),MessageArr.at(11));
    requestMessageByteNum = (quint16)BondTwoUint8ToUint16(requestMessageArr.at(10),requestMessageArr.at(11));
    if(messageArrayByteNum != requestMessageByteNum)
    {
        return false;
    }

    return true;
}

//功能码合法性判断函数
bool ModbusTCP::FuncCodeLegalJudge(const QByteArray &MessageArr)
{
    if(MessageArr.at(7) != 1
            &&MessageArr.at(7) != 2
            &&MessageArr.at(7) != 3
            &&MessageArr.at(7) != 4
            &&MessageArr.at(7) != 5
            &&MessageArr.at(7) != 6
            &&MessageArr.at(7) != 15
            &&MessageArr.at(7) != 16
            &&(quint8)MessageArr.at(7) != 0x81
            &&(quint8)MessageArr.at(7) != 0x83
            &&(quint8)MessageArr.at(7) != 0x8f
            &&(quint8)MessageArr.at(7) != 0x90)
    {
        return false;
    }

    return true;
}

//异常码报文处理函数
//1
void ModbusTCP::TCPExceptionCodeProcess(const QByteArray &MessageArr, QString &exceptionPrompt)
{
    //处理匹配的异常响应码信息
    switch ((quint8)MessageArr.at(7))
    {
    case 0x81:
    {
        exceptionPrompt = "读多个线圈的请求报文出现异常！";
        break;
    }
    case 0x83:
    {
        exceptionPrompt = "读多个寄存器的请求报文出现异常！";
        break;
    }
    case 0x8f:
    {
        exceptionPrompt = "写入多个线圈的请求报文出现异常！";
        break;
    }
    case 0x90:
    {
        exceptionPrompt = "写入多个寄存器的请求报文出现异常！";
        break;
    }
    default:
    {
        exceptionPrompt = "";
        return;
    }
    }

    //获取异常码
    quint8 exceptionCode = (quint8)MessageArr.at(8);

    //异常码判断
    TCPExceptionCodeJudge(exceptionCode);
}
//2
QString ModbusTCP::TCPExceptionCodeProcess(const QByteArray &MessageArr)
{
    QString exceptionPrompt;
    TCPExceptionCodeProcess(MessageArr, exceptionPrompt);

    return exceptionPrompt;
}

//异常码判断函数
//1
void ModbusTCP::TCPExceptionCodeJudge(const quint8 excCode, QString &exceptionCodePrompt)
{
    switch (excCode)
    {
    case 0x01:
    {
        exceptionCodePrompt += " 异常码为：01 非法功能";
        break;
    }
    case 0x02:
    {
        exceptionCodePrompt += " 异常码为：02 非法数据地址";
        break;
    }
    case 0x03:
    {
        exceptionCodePrompt += " 异常码为：03 非法数据值";
        break;
    }
    default:
    {
        exceptionCodePrompt += " 异常码错误！无法解析！";
    }
    }
}
//2
QString ModbusTCP::TCPExceptionCodeJudge(const quint8 excCode)
{
    QString exceptionCodePrompt;
    TCPExceptionCodeJudge(excCode, exceptionCodePrompt);

    return exceptionCodePrompt;
}

//获取当前读出数据操作获得的报文字符串（0X01/0X03)
//1
void ModbusTCP::GetReadMessage(QString &readMess)
{
    readMess = readMessage;
}
//2
QString ModbusTCP::GetReadMessage()
{
    return readMessage;
}
